home *** CD-ROM | disk | FTP | other *** search
/ Computer Shopper 242 / Issue 242 - April 2008 - DPCS0408DVD.ISO / Software Money Savers / VirtualDub / Source / VirtualDub-1.7.7-src.7z / src / Lina / source / main.cpp < prev    next >
Encoding:
C/C++ Source or Header  |  2007-02-28  |  30.1 KB  |  1,055 lines

  1. //    Lina - HTML compiler for VirtualDub help system
  2. //    Copyright (C) 1998-2003 Avery Lee
  3. //
  4. //    This program is free software; you can redistribute it and/or modify
  5. //    it under the terms of the GNU General Public License as published by
  6. //    the Free Software Foundation; either version 2 of the License, or
  7. //    (at your option) any later version.
  8. //
  9. //    This program is distributed in the hope that it will be useful,
  10. //    but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. //    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. //    GNU General Public License for more details.
  13. //
  14. //    You should have received a copy of the GNU General Public License
  15. //    along with this program; if not, write to the Free Software
  16. //    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  17.  
  18. #pragma warning(disable: 4786)
  19.  
  20. #include <sys/stat.h>
  21. #include <direct.h>
  22. #include <stdio.h>
  23. #include <stdarg.h>
  24. #include <ctype.h>
  25. #include <string>
  26. #include <list>
  27. #include <map>
  28. #include <set>
  29. #include <vector>
  30. #include <utility>
  31. #include "document.h"
  32. #include "parser.h"
  33.  
  34. ///////////////////////////////////////////////////////////////////////////
  35.  
  36. std::list<std::pair<std::string, bool> > g_truncateURLs;
  37. std::list<std::string> g_htmlHelpFiles;
  38.  
  39. ///////////////////////////////////////////////////////////////////////////
  40.  
  41. struct Context {
  42.     TreeDocument *mpDocument;
  43.     std::list<const TreeNode *> stack;
  44.     std::list<const TreeNode *> invocation_stack;
  45.     std::list<TreeNode *> construction_stack;
  46.     int pre_count;
  47.     int cdata_count;
  48.     bool eat_next_space;
  49.     bool holding_space;
  50.  
  51.     Context() : pre_count(0), cdata_count(0), eat_next_space(true), holding_space(false) {}
  52.  
  53.     const TreeNode *find_tag(std::string name) {
  54.         std::list<const TreeNode *>::reverse_iterator it(invocation_stack.rbegin()), itEnd(invocation_stack.rend());
  55.         const TreeNode *t = NULL;
  56.         
  57.         for(; it!=itEnd; ++it) {
  58.             t = (*it)->Child(name);
  59.             if (t)
  60.                 break;
  61.             if (!name.empty() && name[0]=='/')
  62.                 break;
  63.         }
  64.  
  65.         return t;
  66.     }
  67. };
  68.  
  69. void output_tag(Context& ctx, std::string *out, const TreeNode& tag);
  70. void output_tag_contents(Context& ctx, std::string *out, const TreeNode& tag);
  71.  
  72. //////////////////////////////////////////////////////////////
  73.  
  74. std::string g_outputDir;
  75.  
  76. typedef std::map<std::string, std::string> tFileCopies;
  77. tFileCopies g_fileCopies;
  78.  
  79. //////////////////////////////////////////////////////////////
  80.  
  81. void error(const Context& ctx, const char *format, ...) {
  82.     va_list val;
  83.  
  84.     std::list<const TreeNode *>::const_reverse_iterator it(ctx.stack.rbegin()), itEnd(ctx.stack.rend());
  85.  
  86.     printf("%s(%d): Error! ", (*it)->mpLocation->mName.c_str(), (*it)->mLineno);
  87.  
  88.     va_start(val, format);
  89.     vprintf(format, val);
  90.     va_end(val);
  91.     putchar('\n');
  92.  
  93.     int indent = 3;
  94.     for(++it; it!=itEnd; ++it) {
  95.         const TreeNode& tag = **it;
  96.         printf("%*c%s(%d): while processing tag <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  97.         indent += 3;
  98.     }
  99.  
  100.     indent = 3;
  101.     for(it=ctx.invocation_stack.rbegin(), itEnd=ctx.invocation_stack.rend(); it!=itEnd; ++it) {
  102.         const TreeNode& tag = **it;
  103.         printf("%*c%s(%d): while invoked from tag <%s> (%d children)\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str(), tag.mChildren.size());
  104.         indent += 3;
  105.     }
  106.  
  107.     exit(10);
  108. }
  109.  
  110. //////////////////////////////////////////////////////////////
  111.  
  112. std::string create_output_filename(const std::string& name) {
  113.     std::string filename(g_outputDir);
  114.  
  115.     if (!filename.empty()) {
  116.         char c = filename[filename.size()-1];
  117.         if (c != '/' && c != '\\')
  118.             filename += '/';
  119.     }
  120.  
  121.     filename += name;
  122.  
  123.     return filename;
  124. }
  125.  
  126. void construct_path(const std::string& dstfile) {
  127.     int idx = -1;
  128.  
  129.     for(;;) {
  130.         int pos = dstfile.find_first_of("\\/", idx+1);
  131.  
  132.         if (pos == std::string::npos)
  133.             break;
  134.  
  135.         std::string partialpath(dstfile.substr(0, pos));
  136.         struct _stat buffer;
  137.  
  138.         if (-1 == _stat(partialpath.c_str(), &buffer)) {
  139.             printf("creating: %s\n", partialpath.c_str());
  140.             _mkdir(partialpath.c_str());
  141.         }
  142.  
  143.         idx = pos;
  144.     }
  145. }
  146.  
  147. void copy_file(const std::string& dstfile, const std::string& srcfile) {
  148.     printf("copying: %s -> %s\n", srcfile.c_str(), dstfile.c_str());
  149.  
  150.     FILE *fs = fopen(srcfile.c_str(), "rb");
  151.  
  152.     if (!fs)
  153.         error("couldn't open source file \"%s\"", srcfile.c_str());
  154.  
  155.     std::string filename(g_outputDir);
  156.  
  157.     if (!filename.empty()) {
  158.         char c = filename[filename.size()-1];
  159.         if (c != '/' && c != '\\')
  160.             filename += '/';
  161.     }
  162.  
  163.     filename += dstfile;
  164.  
  165.     construct_path(filename);
  166.  
  167.     FILE *fd = fopen(filename.c_str(), "wb");
  168.     if (!fd)
  169.         error("couldn't create \"%s\"", filename.c_str());
  170.  
  171.     fseek(fs, 0, SEEK_END);
  172.     std::vector<char> data(ftell(fs));
  173.     fseek(fs, 0, SEEK_SET);
  174.     if (1 != fread(&data.front(), data.size(), 1, fs))
  175.         error("couldn't read from \"%s\"", srcfile.c_str());
  176.     fclose(fs);
  177.  
  178.     if (1 != fwrite(&data.front(), data.size(), 1, fd) || fclose(fd))
  179.         error("couldn't write to \"%s\"", dstfile.c_str());
  180. }
  181.  
  182. bool is_true(const std::string& name) {
  183.     return name.empty() || name[0]=='y' || name[0]=='Y';
  184. }
  185.  
  186. void dump_parse_tree(const TreeNode& tag, int indent = 0) {
  187.     if (tag.mbIsText) {
  188.     } else if (tag.mChildren.empty()) {
  189.         printf("%*c<%s/>\n", indent, ' ', tag.mName.c_str());
  190.     } else {
  191.         printf("%*c<%s>\n", indent, ' ', tag.mName.c_str());
  192.  
  193.         std::list<TreeNode *>::const_iterator it(tag.mChildren.begin()), itEnd(tag.mChildren.end());
  194.         for(; it!=itEnd; ++it) {
  195.             dump_parse_tree(**it, indent+3);
  196.         }
  197.  
  198.         printf("%*c</%s>\n", indent, ' ', tag.mName.c_str());
  199.     }
  200. }
  201.  
  202. ////////////////////////////////////////////////////////////////////////////////
  203.  
  204. void output_tag_attributes(std::string& out, const TreeNode& tag) {
  205.     std::list<TreeAttribute>::const_iterator itAtt(tag.mAttribs.begin()), itAttEnd(tag.mAttribs.end());
  206.     bool is_anchor = (tag.mName == "a");
  207.     
  208.     for(; itAtt!=itAttEnd; ++itAtt) {
  209.         const TreeAttribute& att = *itAtt;
  210.  
  211.         out += ' ';
  212.         out += att.mName;
  213.  
  214.         if (!att.mbNoValue) {
  215.             std::string::const_iterator its(att.mValue.begin()), itsEnd(att.mValue.end());
  216.             for(;its!=itsEnd; ++its)
  217.                 if (!issafevaluechar(*its))
  218.                     break;
  219.  
  220.             std::string value(att.mValue);
  221.  
  222.             if (is_anchor && att.mName == "href") {
  223.                 std::list<std::pair<std::string, bool> >::const_iterator it(g_truncateURLs.begin()), itEnd(g_truncateURLs.end());
  224.  
  225.                 for(; it!=itEnd; ++it) {
  226.                     const std::pair<std::string, bool>& entry = *it;
  227.  
  228.                     if (value.length() >= entry.first.length() && !value.compare(0, entry.first.length(), entry.first)) {
  229.                         if (entry.second) {
  230.                             int l = value.length();
  231.  
  232.                             while(l>0) {
  233.                                 char c = value[--l];
  234.  
  235.                                 if (c == '/' || c == ':')
  236.                                     break;
  237.                                 if (c == '.') {
  238.                                     if (value.substr(l+1, std::string::npos) == "html")
  239.                                         value.erase(l, std::string::npos);
  240.                                     break;
  241.                                 }
  242.                             }
  243.                             printf("truncated link: %s\n", value.c_str());
  244.                         }
  245.                         break;
  246.                     }
  247.                 }
  248.             }
  249.  
  250.             if (att.mValue.empty() || its!=itsEnd) {
  251.                 out += "=\"";
  252.                 out += value;
  253.                 out += '"';
  254.             } else {
  255.                 out += '=';
  256.                 out += value;
  257.             }
  258.         }
  259.     }
  260. }
  261.  
  262. void output_tag_contents(Context& ctx, std::string *out, const TreeNode& tag) {
  263.     static int recursion_depth = 0;
  264.  
  265.     ++recursion_depth;
  266.  
  267.     if (recursion_depth > 64)
  268.         error(ctx, "recursion exceeded limits");
  269.  
  270.     std::list<TreeNode *>::const_iterator it(tag.mChildren.begin()), itEnd(tag.mChildren.end());
  271.     for(; it!=itEnd; ++it) {
  272.         output_tag(ctx, out, **it);
  273.     }
  274.  
  275.     --recursion_depth;
  276. }
  277.  
  278. void output_standard_tag(Context& ctx, std::string *out, const TreeNode& tag) {
  279.     if (!tag.mbIsText && tag.mName == "pre")
  280.         ++ctx.pre_count;
  281.  
  282.     if (out && (tag.mbIsControl || !tag.mbIsText)) {
  283.         if (ctx.holding_space && ctx.cdata_count) {
  284.             if (!ctx.eat_next_space) {
  285.                 *out += ' ';
  286.             }
  287.             ctx.eat_next_space = false;
  288.             ctx.holding_space = false;
  289.         }
  290.     }
  291.  
  292.     if (!ctx.construction_stack.empty()) {
  293.         TreeNode *new_tag = ctx.mpDocument->AllocNode();
  294.  
  295.         new_tag->mpLocation        = tag.mpLocation;
  296.         new_tag->mLineno        = tag.mLineno;
  297.         new_tag->mName            = tag.mName;
  298.         new_tag->mAttribs        = tag.mAttribs;
  299.         new_tag->mbIsText        = tag.mbIsText;
  300.         new_tag->mbIsControl    = tag.mbIsControl;
  301.  
  302.         ctx.construction_stack.back()->mChildren.push_back(new_tag);
  303.         ctx.construction_stack.push_back(new_tag);
  304.  
  305.         output_tag_contents(ctx, out, tag);
  306.  
  307.         ctx.construction_stack.pop_back();
  308.     } else if (tag.mbIsText) {
  309.         if (out) {
  310.             if (tag.mbIsControl) {
  311.                 *out += tag.mName;
  312.             } else if (ctx.cdata_count) {
  313.                 if (ctx.pre_count) {
  314.                     *out += tag.mName;
  315.                 } else {
  316.                     std::string::const_iterator it(tag.mName.begin()), itEnd(tag.mName.end());
  317.  
  318.                     for(; it!=itEnd; ++it) {
  319.                         const char c = *it;
  320.  
  321.                         if (isspace(c)) {
  322.                             ctx.holding_space = true;
  323.                         } else {
  324.                             if (ctx.eat_next_space)
  325.                                 ctx.eat_next_space = false;
  326.                             else if (ctx.holding_space)
  327.                                 *out += ' ';
  328.  
  329.                             ctx.holding_space = false;
  330.                             *out += c;
  331.                         }
  332.                     }
  333.                 }
  334.             } else {
  335.                 std::string::const_iterator it(tag.mName.begin()), itEnd(tag.mName.end());
  336.  
  337.                 for(; it!=itEnd; ++it) {
  338.                     const char c = *it;
  339.  
  340.                     if (!isspace(c))
  341.                         error(ctx, "inline text not allowed");
  342.                 }
  343.             }
  344.         }
  345.     } else {
  346.         bool cdata = tag.SupportsCDATA();
  347.  
  348.         if (cdata) {
  349.             if (!ctx.cdata_count) {
  350.                 ctx.holding_space = false;
  351.                 ctx.eat_next_space = true;
  352.             }
  353.             ++ctx.cdata_count;
  354.         }
  355.  
  356.         if (!out) {
  357.             output_tag_contents(ctx, out, tag);
  358.         } else if (tag.mChildren.empty()) {
  359.             *out += '<';
  360.             *out += tag.mName;
  361.             output_tag_attributes(*out, tag);
  362.             *out += '>';
  363.         } else {
  364.             *out += '<';
  365.             *out += tag.mName;
  366.             output_tag_attributes(*out, tag);
  367.             *out += '>';
  368.  
  369.             output_tag_contents(ctx, out, tag);
  370.  
  371.             *out += "</";
  372.             *out += tag.mName;
  373.             *out += '>';
  374.         }
  375.  
  376.         if (cdata)
  377.             --ctx.cdata_count;
  378.     }
  379.     if (!tag.mbIsText && tag.mName == "pre")
  380.         --ctx.pre_count;
  381. }
  382.  
  383. std::string HTMLize(const std::string& s) {
  384.     std::string::const_iterator it(s.begin()), itEnd(s.end());
  385.     std::string t;
  386.  
  387.     for(; it!=itEnd; ++it) {
  388.         char c = *it;
  389.  
  390.         switch(c) {
  391.         case '"':    t.append("""); break;
  392.         case '<':    t.append("<"); break;
  393.         case '>':    t.append(">"); break;
  394.         case '&':    t.append("&"); break;
  395.         default:    t += c; break;
  396.         }
  397.     }
  398.  
  399.     return t;
  400. }
  401.  
  402. void output_source_tags(Context& ctx, std::string *out, const TreeNode& tag) {
  403.     std::string s;
  404.  
  405.     if (tag.mbIsText)
  406.         s = tag.mName;
  407.     else if (tag.mChildren.empty()) {
  408.         s = '<';
  409.         s += tag.mName;
  410.         output_tag_attributes(s, tag);
  411.         s += '>';
  412.     } else {
  413.         s = '<';
  414.         s += tag.mName;
  415.         output_tag_attributes(s, tag);
  416.         s += '>';
  417.  
  418.         out->append(HTMLize(s));
  419.  
  420.         out->append("<ul marker=none>");
  421.  
  422.         std::list<TreeNode *>::const_iterator itBegin(tag.mChildren.begin()), it(itBegin), itEnd(tag.mChildren.end());
  423.         for(; it!=itEnd; ++it) {
  424.         out->append("<li>");
  425.             output_source_tags(ctx, out, **it);
  426.         out->append("</li>");
  427.         }
  428.  
  429.         out->append("</ul>");
  430.  
  431.         s = "</";
  432.         s += tag.mName;
  433.         s += '>';
  434.     }
  435.  
  436.     out->append(HTMLize(s));
  437.  
  438.     if (!tag.mbIsText)
  439.         out->append("<br>");
  440. }
  441.  
  442. void dump_stack(Context& ctx) {
  443.     std::list<const TreeNode *>::reverse_iterator it(ctx.stack.rbegin()), itEnd(ctx.stack.rend());
  444.  
  445.     printf("Current execution stack:\n");
  446.     int indent = 3;
  447.     for(++it; it!=itEnd; ++it) {
  448.         const TreeNode& tag = **it;
  449.         printf("%*c%s(%d): processing <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  450.         indent += 3;
  451.     }
  452.  
  453.     indent = 3;
  454.     std::list<TreeNode *>::reverse_iterator it2(ctx.construction_stack.rbegin()), it2End(ctx.construction_stack.rend());
  455.     for(; it2!=it2End; ++it2) {
  456.         const TreeNode& tag = **it2;
  457.         printf("%*c%s(%d): while creating tag <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  458.         indent += 3;
  459.     }
  460.  
  461.     indent = 3;
  462.     for(it=ctx.invocation_stack.rbegin(), itEnd=ctx.invocation_stack.rend(); it!=itEnd; ++it) {
  463.         const TreeNode& tag = **it;
  464.         printf("%*c%s(%d): while invoked from tag <%s>\n", indent, ' ', tag.mpLocation->mName.c_str(), tag.mLineno, tag.mName.c_str());
  465.         indent += 3;
  466.     }
  467. }
  468.  
  469. void output_toc_children(FILE *f, const TreeNode& node);
  470.  
  471. void output_toc_node(FILE *f, const TreeNode& node) {
  472.     if (node.mName != "tocnode")
  473.         error("Invalid node <%s> found during HTML help TOC generation", node.mName.c_str());
  474.  
  475.     const TreeAttribute *attrib = node.Attrib("name");
  476.  
  477.     if (!attrib || attrib->mbNoValue)
  478.         error("<tocnode> must have NAME attribute");
  479.  
  480.     const TreeAttribute *target = node.Attrib("target");
  481.  
  482.     fputs("<LI><OBJECT type=\"text/sitemap\">\n", f);
  483.     fprintf(f, "<param name=\"Name\" value=\"%s\">\n", HTMLize(attrib->mValue).c_str());
  484.     if (target && !target->mbNoValue)
  485.         fprintf(f, "<param name=\"Local\" value=\"%s\">\n", HTMLize(target->mValue).c_str());
  486.     fputs("</OBJECT>\n", f);
  487.  
  488.     output_toc_children(f, node);
  489. }
  490.  
  491. void output_toc_children(FILE *f, const TreeNode& node) {
  492.     bool nodesFound = false;
  493.  
  494.     TreeNode::Children::const_iterator it(node.mChildren.begin()), itEnd(node.mChildren.end());
  495.     for(; it!=itEnd; ++it) {
  496.         const TreeNode& childNode = **it;
  497.  
  498.         if (!childNode.mbIsText) {
  499.             if (!nodesFound) {
  500.                 nodesFound = true;
  501.                 fputs("<UL>\n", f);
  502.             }
  503.  
  504.             output_toc_node(f, childNode);
  505.         }
  506.     }
  507.  
  508.     if (nodesFound)
  509.         fputs("</UL>\n", f);
  510. }
  511.  
  512. void output_toc(FILE *f, const TreeNode& root) {
  513.     fputs(    "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML//EN\">\n"
  514.             "<HTML>\n"
  515.             "<HEAD>\n"
  516.             "<!-- Sitemap 1.0 -->\n"
  517.             "</HEAD><BODY>\n"
  518.             "<OBJECT type=\"text/site properties\">\n"
  519.             "\t<param name=\"ImageType\" value=\"Folder\">\n"
  520.             "</OBJECT>\n"
  521.         , f);
  522.  
  523.     output_toc_children(f, root);
  524.  
  525.     fputs(    "</BODY>\n"
  526.             "</HTML>\n"
  527.         , f);
  528. }
  529.  
  530. void output_special_tag(Context& ctx, std::string *out, const TreeNode& tag) {
  531.     if (tag.mName == "lina:fireball") {
  532.         const TreeAttribute *a1 = tag.Attrib("src");
  533.         const TreeAttribute *a2 = tag.Attrib("dst");
  534.  
  535.         if (!a1 || !a2)
  536.             error(ctx, "<lina:fireball> requires SRC and DST attributes");
  537.  
  538.         g_fileCopies[a2->mValue] = a1->mValue;
  539.     } else if (tag.mName == "lina:write") {
  540.         const TreeAttribute *a = tag.Attrib("file");
  541.  
  542.         if (!a)
  543.             error(ctx, "<lina:write> must specify FILE");
  544.  
  545.         std::string s;
  546.  
  547.         std::list<TreeNode *> tempStack;
  548.         ctx.construction_stack.swap(tempStack);
  549.         int cdataCount = ctx.cdata_count;
  550.         int preCount = ctx.pre_count;
  551.         ctx.cdata_count = 0;
  552.         ctx.pre_count = 0;
  553.         bool bHoldingSpace = ctx.holding_space;
  554.         bool bEatNextSpace = ctx.eat_next_space;
  555.         ctx.holding_space = false;
  556.         ctx.eat_next_space = true;
  557.         output_tag_contents(ctx, &s, tag);
  558.         ctx.holding_space = bHoldingSpace;
  559.         ctx.eat_next_space = bEatNextSpace;
  560.         ctx.pre_count = cdataCount;
  561.         ctx.cdata_count = preCount;
  562.         ctx.construction_stack.swap(tempStack);
  563.  
  564.         std::string filename(create_output_filename(a->mValue));
  565.  
  566.         FILE *f = fopen(filename.c_str(), "wb");
  567.         if (!f)
  568.             error(ctx, "couldn't create \"%s\"", a->mValue.c_str());
  569.         fwrite(s.data(), s.length(), 1, f);
  570.         fclose(f);
  571.  
  572.         printf("created file: %s\n", a->mValue.c_str());
  573.     } else if (tag.mName == "lina:body") {
  574.  
  575. //        printf("outputting:\n");
  576. //        dump_parse_tree(*ctx.invocation_stack.back(), 4);
  577.  
  578.         output_tag_contents(ctx, out, *ctx.invocation_stack.back());
  579.     } else if (tag.mName == "lina:tag") {
  580.         const TreeAttribute *a = tag.Attrib("name");
  581.         if (!a)
  582.             error(ctx, "<lina:tag> must have NAME attribute");
  583.  
  584.         ctx.construction_stack.push_back(ctx.mpDocument->AllocNode());
  585.         TreeNode *new_tag = ctx.construction_stack.back();
  586.  
  587.         new_tag->mpLocation = tag.mpLocation;
  588.         new_tag->mLineno = tag.mLineno;
  589.         new_tag->mName = a->mValue;
  590.         new_tag->mbIsText = false;
  591.         new_tag->mbIsControl = false;
  592.  
  593.         output_tag_contents(ctx, NULL, tag);
  594.  
  595.         ctx.construction_stack.pop_back();
  596.         output_tag(ctx, out, *new_tag);
  597.     } else if (tag.mName == "lina:arg") {
  598.         if (!out && ctx.construction_stack.empty())
  599.             error(ctx, "<lina:arg> can only be used in an output context");
  600.         const TreeAttribute *a = tag.Attrib("name");
  601.         if (!a)
  602.             error(ctx, "<lina:arg> must have NAME attribute");
  603.  
  604.         if (ctx.invocation_stack.empty())
  605.             error(ctx, "<lina:arg> can only be used during macro expansion");
  606.  
  607.         std::list<const TreeNode *>::const_iterator it(ctx.invocation_stack.end());
  608.         --it;
  609.  
  610.         int levels = 1;
  611.         const char *name = a->mValue.c_str();
  612.         while(*name == '^') {
  613.             ++levels;
  614.             ++name;
  615.  
  616.             if (it == ctx.invocation_stack.begin())
  617.                 error(ctx, "Number of up-scope markers in name exceeds macro nesting level");
  618.  
  619.             --it;
  620.         }
  621.  
  622.         const TreeNode& macrotag = **it;
  623.         const TreeAttribute *a2 = macrotag.Attrib(name);
  624.         if (!a2)
  625.             error(ctx, "macro invocation <%s> does not have an attribute \"%s\"", macrotag.mName.c_str(), name);
  626.  
  627.         if (out) {
  628.             *out += a2->mValue;
  629.  
  630.             ctx.eat_next_space = false;
  631.             ctx.holding_space = false;
  632.         } else {
  633.             TreeNode *t = ctx.mpDocument->AllocNode();
  634.  
  635.             t->mpLocation = tag.mpLocation;
  636.             t->mLineno = tag.mLineno;
  637.             t->mbIsControl = false;
  638.             t->mbIsText = true;
  639.             t->mName = a2->mValue;
  640.  
  641.             ctx.construction_stack.back()->mChildren.push_back(t);
  642.         }
  643.     } else if (tag.mName == "lina:if-arg") {
  644.         const TreeAttribute *a = tag.Attrib("name");
  645.         if (!a)
  646.             error(ctx, "<lina:if-arg> must have NAME attribute");
  647.  
  648.         if (ctx.invocation_stack.empty())
  649.             error(ctx, "<lina:if-arg> can only be used during macro expansion");
  650.  
  651.         const TreeNode& macrotag = *ctx.invocation_stack.back();
  652.         const TreeAttribute *a2 = macrotag.Attrib(a->mValue);
  653.         if (a2)
  654.             output_tag_contents(ctx, out, tag);
  655.     } else if (tag.mName == "lina:if-not-arg") {
  656.         const TreeAttribute *a = tag.Attrib("name");
  657.         if (!a)
  658.             error(ctx, "<lina:if-not-arg> must have NAME attribute");
  659.  
  660.         if (ctx.invocation_stack.empty())
  661.             error(ctx, "<lina:if-not-arg> can only be used during macro expansion");
  662.  
  663.         const TreeNode& macrotag = *ctx.invocation_stack.back();
  664.         const TreeAttribute *a2 = macrotag.Attrib(a->mValue);
  665.         if (!a2)
  666.             output_tag_contents(ctx, out, tag);
  667.     } else if (tag.mName == "lina:attrib") {
  668.         if (ctx.construction_stack.empty())
  669.             error(ctx, "<lina:attrib> can only be used in a <lina:tag> element");
  670.  
  671.         const TreeAttribute *a = tag.Attrib("name");
  672.         if (!a)
  673.             error(ctx, "<lina:attrib> must have NAME attribute");
  674.  
  675.         std::string s;
  676.         std::list<TreeNode *> tempStack;
  677.         ctx.construction_stack.swap(tempStack);
  678.         ++ctx.cdata_count;
  679.         ++ctx.pre_count;
  680.         bool bHoldingSpace = ctx.holding_space;
  681.         bool bEatNextSpace = ctx.eat_next_space;
  682.         ctx.holding_space = false;
  683.         ctx.eat_next_space = true;
  684.         output_tag_contents(ctx, &s, tag);
  685.         ctx.holding_space = bHoldingSpace;
  686.         ctx.eat_next_space = bEatNextSpace;
  687.         --ctx.pre_count;
  688.         --ctx.cdata_count;
  689.         ctx.construction_stack.swap(tempStack);
  690.  
  691.         TreeNode *t = ctx.construction_stack.back();
  692.         TreeAttribute new_att;
  693.         if (tag.Attrib("novalue")) {
  694.             new_att.mbNoValue = true;
  695.         } else {
  696.             new_att.mbNoValue = false;
  697.             new_att.mValue = s;
  698.         }
  699.         new_att.mName = a->mValue;
  700.         t->mAttribs.push_back(new_att);
  701.     } else if (tag.mName == "lina:pull") {
  702.         if (ctx.invocation_stack.empty())
  703.             error(ctx, "<lina:pull> can only be used during macro expansion");
  704.  
  705.         const TreeAttribute *a = tag.Attrib("name");
  706.         if (!a)
  707.             error(ctx, "<lina:pull> must have NAME attribute");
  708.  
  709.         const TreeNode *t = ctx.find_tag(a->mValue);
  710.         
  711.         if (!t)
  712.             error(ctx, "cannot find tag <%s> referenced in <lina:pull>", a->mValue.c_str());
  713.  
  714.         output_tag_contents(ctx, out, *t);        
  715.     } else if (tag.mName == "lina:for-each") {
  716.         const TreeAttribute *a = tag.Attrib("name");
  717.         if (!a)
  718.             error(ctx, "<lina:for-each> must have NAME attribute");
  719.         
  720.         std::string node_name;
  721.         const TreeNode *parent;
  722.         if (ctx.invocation_stack.empty()) {
  723.             if (!a->mValue.empty() && a->mValue[0] == '/')
  724.                 parent = ctx.mpDocument->mpRoot->ResolvePath(a->mValue.substr(1), node_name);
  725.             else
  726.                 error(ctx, "path must be absolute if not in macro context");
  727.         } else {
  728.             std::list<const TreeNode *>::reverse_iterator it(ctx.invocation_stack.rbegin()), itEnd(ctx.invocation_stack.rend());
  729.             
  730.             for(; it!=itEnd; ++it) {
  731.                 parent = (*it)->ResolvePath(a->mValue, node_name);
  732.                 if(parent)
  733.                     break;
  734.                 if (!a->mValue.empty() && a->mValue[0] == '/')
  735.                     break;
  736.             }
  737.         }
  738.  
  739.         if (!parent)
  740.             error(ctx, "cannot resolve path \"%s\"", a->mValue.c_str());
  741.  
  742.         std::list<TreeNode *>::const_iterator it2(parent->mChildren.begin()), it2End(parent->mChildren.end());
  743.  
  744.         ctx.invocation_stack.push_back(NULL);
  745.         for(; it2!=it2End; ++it2) {
  746.             if ((*it2)->mName == node_name) {
  747.                 ctx.invocation_stack.back() = *it2;
  748.                 output_tag_contents(ctx, out, tag);
  749.             }
  750.         }
  751.         ctx.invocation_stack.pop_back();
  752.     } else if (tag.mName == "lina:apply") {
  753.         const TreeAttribute *a = tag.Attrib("name");
  754.         if (!a)
  755.             error(ctx, "<lina:apply> must have NAME attribute");
  756.  
  757.         std::map<std::string, TreeNode *>::const_iterator it(ctx.mpDocument->mMacros.find(a->mValue));
  758.  
  759.         if (it == ctx.mpDocument->mMacros.end())
  760.             error(ctx, "macro \"%s\" undeclared", a->mValue.c_str());
  761.         
  762.         std::list<TreeNode *>::const_iterator it2(tag.mChildren.begin()), it2End(tag.mChildren.end());
  763.  
  764.         ctx.invocation_stack.push_back(NULL);
  765.         for(; it2!=it2End; ++it2) {
  766.             if (!(*it2)->mbIsText) {
  767.                 ctx.invocation_stack.back() = *it2;
  768.                 output_tag_contents(ctx, out, *(*it).second);
  769.             }
  770.         }
  771.         ctx.invocation_stack.pop_back();
  772.     } else if (tag.mName == "lina:if-present") {
  773.         if (ctx.invocation_stack.empty())
  774.             error(ctx, "<lina:if-present> can only be used during macro expansion");
  775.         const TreeAttribute *a = tag.Attrib("name");
  776.         if (!a)
  777.             error(ctx, "<lina:if-present> must have NAME attribute");
  778.  
  779.         const TreeNode *t = ctx.find_tag(a->mValue);
  780.         if (t)
  781.             output_tag_contents(ctx, out, tag);
  782.     } else if (tag.mName == "lina:if-not-present") {
  783.         if (ctx.invocation_stack.empty())
  784.             error(ctx, "<lina:if-not-present> can only be used during macro expansion");
  785.         const TreeAttribute *a = tag.Attrib("name");
  786.         if (!a)
  787.             error(ctx, "<lina:if-not-present> must have NAME attribute");
  788.  
  789.         const TreeNode *t = ctx.find_tag(a->mValue);
  790.         if (!t)
  791.             output_tag_contents(ctx, out, tag);
  792.     } else if (tag.mName == "lina:pre") {
  793.         ++ctx.pre_count;
  794.         ++ctx.cdata_count;
  795.         if (!out)
  796.             output_standard_tag(ctx, out, tag);
  797.         else {
  798.             output_tag_contents(ctx, out, tag);
  799.         }
  800.         --ctx.cdata_count;
  801.         --ctx.pre_count;
  802.     } else if (tag.mName == "lina:cdata") {
  803.         ++ctx.cdata_count;
  804.         if (!out)
  805.             output_standard_tag(ctx, out, tag);
  806.         else
  807.             output_tag_contents(ctx, out, tag);
  808.         --ctx.cdata_count;
  809.     } else if (tag.mName == "lina:delay") {
  810.         std::list<TreeNode *>::const_iterator it(tag.mChildren.begin()), itEnd(tag.mChildren.end());
  811.         for(; it!=itEnd; ++it) {
  812.             output_standard_tag(ctx, out, **it);
  813.         }
  814.     } else if (tag.mName == "lina:dump-stack") {
  815.         dump_stack(ctx);
  816.     } else if (tag.mName == "lina:replace") {
  817.         const TreeAttribute *a = tag.Attrib("from");
  818.         if (!a || a->mbNoValue)
  819.             error(ctx, "<lina:replace> must have FROM attribute");
  820.         const TreeAttribute *a2 = tag.Attrib("to");
  821.         if (!a2 || a2->mbNoValue)
  822.             error(ctx, "<lina:replace> must have TO attribute");
  823.  
  824.         const std::string& x = a->mValue;
  825.         const std::string& y = a2->mValue;
  826.  
  827.         std::string s, t;
  828.         std::string::size_type i = 0;
  829.  
  830.         output_tag_contents(ctx, &s, tag);
  831.  
  832.         for(;;) {
  833.             std::string::size_type j = s.find(x, i);
  834.             if (j != i)
  835.                 t.append(s, i, j-i);
  836.             if (j == std::string::npos)
  837.                 break;
  838.             t.append(y);
  839.             i = j + x.size();
  840.         }
  841.  
  842.         TreeNode *new_tag = ctx.mpDocument->AllocNode();
  843.  
  844.         new_tag->mpLocation = tag.mpLocation;
  845.         new_tag->mLineno = tag.mLineno;
  846.         new_tag->mbIsText = true;
  847.         new_tag->mbIsControl = false;
  848.         new_tag->mName = t;
  849.  
  850.         output_tag(ctx, out, *new_tag);
  851.     } else if (tag.mName == "lina:set-option") {
  852.         const TreeAttribute *a_name = tag.Attrib("name");
  853.         if (!a_name)
  854.             error(ctx, "<lina:set-option> must have NAME attribute");
  855.  
  856.         if (a_name->mValue == "link-truncate") {
  857.             const TreeAttribute *a_val = tag.Attrib("baseurl");
  858.             if (!a_val || a_val->mbNoValue)
  859.                 error(ctx, "option \"link-truncate\" requires BASEURL attribute");
  860.  
  861.             bool bTruncate = !tag.Attrib("notruncate");
  862.  
  863.             g_truncateURLs.push_back(std::make_pair(a_val->mValue, bTruncate));
  864.         } else if (a_name->mValue == "output-dir") {
  865.             const TreeAttribute *a_val = tag.Attrib("target");
  866.             if (!a_val || a_val->mbNoValue)
  867.                 error(ctx, "option \"output-dir\" requires TARGET attribute");
  868.  
  869.             g_outputDir = a_val->mValue;
  870.         } else if (a_name->mValue == "tag-info") {
  871.             const TreeAttribute *a_tagname = tag.Attrib("tag");
  872.             if (!a_tagname || a_tagname->mbNoValue)
  873.                 error(ctx, "option \"tag-info\" requires TAG attribute");
  874.  
  875.             const TreeAttribute *a_cdata = tag.Attrib("cdata");
  876.  
  877.             if (!a_cdata || a_cdata->mbNoValue)
  878.                 error(ctx, "option \"tag-info\" requires CDATA attribute");
  879.  
  880.             TreeNode::SetSupportsCDATA(a_tagname->mValue, is_true(a_cdata->mValue));
  881.         } else
  882.             error(ctx, "option \"%s\" unknown\n", a_name->mValue.c_str());
  883.  
  884.     } else if (tag.mName == "lina:data") {
  885.         // do nothing
  886.     } else if (tag.mName == "lina:source") {
  887.         if (out) {
  888.             std::list<TreeNode *>::const_iterator itBegin(tag.mChildren.begin()), it(itBegin), itEnd(tag.mChildren.end());
  889.             for(; it!=itEnd; ++it) {
  890.                 output_source_tags(ctx, out, **it);
  891.             }
  892.         }
  893.     } else if (tag.mName == "lina:htmlhelp-toc") {
  894.         const TreeAttribute *a_val = tag.Attrib("file");
  895.         if (!a_val || a_val->mbNoValue)
  896.             error(ctx, "<lina:htmlhelp-toc> requires FILE attribute");
  897.  
  898.         const std::string filename(create_output_filename(a_val->mValue));
  899.  
  900.         // build new tag with TOC contents
  901.         ctx.construction_stack.push_back(ctx.mpDocument->AllocNode());
  902.         TreeNode *new_tag = ctx.construction_stack.back();
  903.  
  904.         new_tag->mpLocation = tag.mpLocation;
  905.         new_tag->mLineno = tag.mLineno;
  906.         new_tag->mName = a_val->mValue;
  907.         new_tag->mbIsText = false;
  908.         new_tag->mbIsControl = false;
  909.  
  910.         output_tag_contents(ctx, NULL, tag);
  911.  
  912.         ctx.construction_stack.pop_back();
  913.         output_tag(ctx, out, *new_tag);
  914.  
  915.         FILE *f = fopen(filename.c_str(), "wb");
  916.         if (!f)
  917.             error(ctx, "couldn't create htmlhelp toc \"%s\"", a_val->mValue.c_str());
  918.         output_toc(f, *new_tag);
  919.         fclose(f);
  920.  
  921.     } else if (tag.mName == "lina:htmlhelp-project") {
  922.         const TreeAttribute *file_val = tag.Attrib("file");
  923.         if (!file_val || file_val->mbNoValue)
  924.             error(ctx, "<lina:htmlhelp-project> requires FILE attribute");
  925.  
  926.         const TreeAttribute *output_val = tag.Attrib("output");
  927.         if (!output_val || output_val->mbNoValue)
  928.             error(ctx, "<lina:htmlhelp-project> requires OUTPUT attribute");
  929.  
  930.         const TreeAttribute *toc_val = tag.Attrib("toc");
  931.         if (!toc_val || toc_val->mbNoValue)
  932.             error(ctx, "<lina:htmlhelp-project> requires TOC attribute");
  933.  
  934.         const TreeAttribute *title_val = tag.Attrib("title");
  935.         if (!title_val || title_val->mbNoValue)
  936.             error(ctx, "<lina:htmlhelp-project> requires TITLE attribute");
  937.  
  938.         const std::string filename(create_output_filename(file_val->mValue));
  939.  
  940.         FILE *f = fopen(filename.c_str(), "wb");
  941.         if (!f)
  942.             error(ctx, "couldn't create htmlhelp project \"%s\"", file_val->mValue.c_str());
  943.         fprintf(f,
  944.             "[OPTIONS]\n"
  945.             "Auto Index=Yes\n"
  946.             "Compatibility=1.1 or later\n"
  947.             "Compiled file=%s\n"
  948.             "Contents file=%s\n"
  949.             "Default topic=index.html\n"
  950.             "Display compile progress=no\n"
  951.             "Full-text search=Yes\n"
  952.             , output_val->mValue.c_str()
  953.             , toc_val->mValue.c_str()
  954.             );
  955.  
  956.  
  957.         const TreeAttribute *fullstop_val = tag.Attrib("fullstop");
  958.         if (fullstop_val && !fullstop_val->mbNoValue)
  959.             fprintf(f, "Full text search stop list file=%s\n", fullstop_val->mValue.c_str());
  960.  
  961.         fprintf(f,
  962.             "Language=0x0409 English (United States)\n"
  963.             "Title=%s\n"
  964.             "\n"
  965.             "[FILES]\n"
  966.             , title_val->mValue.c_str()
  967.             );
  968.  
  969.         std::list<std::string>::const_iterator it(g_htmlHelpFiles.begin()), itEnd(g_htmlHelpFiles.end());
  970.         for(; it!=itEnd; ++it) {
  971.             fprintf(f, "%s\n", (*it).c_str());
  972.         }
  973.  
  974.         fclose(f);        
  975.     } else if (tag.mName == "lina:htmlhelp-addfile") {
  976.         const TreeAttribute *file_val = tag.Attrib("file");
  977.         if (!file_val || file_val->mbNoValue)
  978.             error(ctx, "<lina:htmlhelp-addfile> requires FILE attribute");
  979.  
  980.         g_htmlHelpFiles.push_back(file_val->mValue);
  981.     } else {
  982.         std::string macroName(tag.mName, 5, std::string::npos);
  983.         std::map<std::string, TreeNode *>::const_iterator it = ctx.mpDocument->mMacros.find(macroName);
  984.  
  985.         if (it == ctx.mpDocument->mMacros.end())
  986.             error(ctx, "macro <lina:%s> not found", macroName.c_str());
  987.  
  988. //        dump_stack(ctx);
  989. //        printf("executing macro: %s (%s:%d)\n", tag.mName.c_str(), tag.mLocation->name.c_str(), tag.mLineno);
  990.  
  991.         ctx.invocation_stack.push_back(&tag);
  992.         output_tag_contents(ctx, out, *(*it).second);
  993.         ctx.invocation_stack.pop_back();
  994.  
  995. //        printf("exiting macro: %s (%s:%d)\n", tag.mName.c_str(), tag.mLocation->name.c_str(), tag.mLineno);
  996.     }
  997. }
  998.  
  999. void output_tag(Context& ctx, std::string *out, const TreeNode& tag) {
  1000.     ctx.stack.push_back(&tag);
  1001.  
  1002.     if (!tag.mbIsText && !tag.mName.compare(0,5,"lina:")) {
  1003.         output_special_tag(ctx, out, tag);
  1004.     } else {
  1005.         output_standard_tag(ctx, out, tag);
  1006.     }
  1007.  
  1008.     ctx.stack.pop_back();
  1009. }
  1010.  
  1011. int main(int argc, char **argv) {
  1012.     TreeNode::SetSupportsCDATA("p",        true);
  1013.     TreeNode::SetSupportsCDATA("h1",        true);
  1014.     TreeNode::SetSupportsCDATA("h2",        true);
  1015.     TreeNode::SetSupportsCDATA("h3",        true);
  1016.     TreeNode::SetSupportsCDATA("h4",        true);
  1017.     TreeNode::SetSupportsCDATA("h5",        true);
  1018.     TreeNode::SetSupportsCDATA("h6",        true);
  1019.     TreeNode::SetSupportsCDATA("td",        true);
  1020.     TreeNode::SetSupportsCDATA("th",        true);
  1021.     TreeNode::SetSupportsCDATA("li",        true);
  1022.     TreeNode::SetSupportsCDATA("style",    true);
  1023.     TreeNode::SetSupportsCDATA("script",    true);
  1024.     TreeNode::SetSupportsCDATA("title",    true);
  1025.     TreeNode::SetSupportsCDATA("div",        true);
  1026.     TreeNode::SetSupportsCDATA("dt",        true);
  1027.     TreeNode::SetSupportsCDATA("dd",        true);
  1028.  
  1029.     TreeDocument doc;
  1030.     TreeParser parser(&doc);
  1031.  
  1032.     parser.ParseFile(argv[1]);
  1033.  
  1034. //    dump_parse_tree(*doc.mpRoot);
  1035.  
  1036.     Context ctx;
  1037.  
  1038.     ctx.mpDocument = &doc;
  1039.  
  1040.     output_tag(ctx, NULL, *doc.mpRoot);
  1041.  
  1042.     // copy files
  1043.  
  1044.     tFileCopies::const_iterator it(g_fileCopies.begin()), itEnd(g_fileCopies.end());
  1045.  
  1046.     for(; it!=itEnd; ++it) {
  1047.         const tFileCopies::value_type& info = *it;
  1048.  
  1049.         copy_file(info.first, info.second);
  1050.     }
  1051.  
  1052.     printf("No errors.\n");
  1053.     return 0;
  1054. }
  1055.